<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>script.aculo.us Unit test file</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <script src="../../lib/prototype.js" type="text/javascript"></script>
  <script src="../../src/scriptaculous.js" type="text/javascript"></script>
  <script src="../../src/unittest.js" type="text/javascript"></script>
  <link rel="stylesheet" href="../test.css" type="text/css" />
  <style type="text/css" media="screen">
    #rotfl {
      color: red;
      font-family: serif;
      font-style: italic;
      font-size: 40px;
      background: #fed;
      padding: 1em;
      width: 400px;
    } 
    .final {
      color: #fff;
      font-style: italic;
      background: #000;
      opacity: 0.5;
    }
    body div.final {
      font-size: 20px;
    }
  </style>
</head>
<body>
<h1>script.aculo.us Unit test file</h1>
<p>
  Tests for effects.js
</p>

<!-- generated elements go in here -->
<div id="sandbox"></div>

<!-- Log output -->
<div id="testlog"> </div>

<div class="morphing blub" style="font-size:25px;color:#f00">Well</div>
<div class="morphing">You know</div>
<div id="blah" style="border:1px solid black;width:100px">Whoo-hoo!</div>

<div id="error_message">ERROR MESSAGE</div>
<div id="error_message_2">SECOND ERROR MESSAGE</div>
<div id="error_message_3" style="border:1px solid red; width:100px; color: #f00">THIRD ERROR MESSAGE</div>

<ul class="error-list" id="error_test_ul">
  <li>Lorem ipsum dolor sit amet, consectetur adipisicing elit,</li>
  <li>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</li>
  <li>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris</li>
  <li>nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in</li>
  <li>reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</li>
</ul>

<div id="rotfl">ROTFL</div>

<div id="tween">foo!</div>

<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[

  var TAGS =
    ['div','span','ol','ul','table','p','h1','h2','h3','h4','h5','h6'];

  var COMBINED_EFFECTS =
    ['Fade','Appear','BlindUp','BlindDown','Puff','SwitchOff','DropOut','Shake',
     'SlideUp','SlideDown','Pulsate','Squish','Fold','Grow','Shrink'];
  
  var COMBINED_RJS_EFFECTS = $w('fade appear blind_up blind_down puff switch_off '+
    'drop_out shake slide_up slide_down pulsate squish fold grow shrink');
     
  var tmp, tmp2;

  new Test.Unit.Runner({

    setup: function() { with (this) {
      $('sandbox').innerHTML = "";
    }},
    
    teardown: function() { with(this) {
      // remove all queued effects
      Effect.Queue.each(function(e) { e.cancel() });
    }},
    
    testExceptionOnNonExistingElement: function() { with(this) {
      assertRaise('ElementDoesNotExistError', 
        function(){new Effect.Opacity('nothing-to-see-here')});
      assertRaise('ElementDoesNotExistError', 
        function(){new Effect.Move('nothing-to-see-here')});
      assertRaise('ElementDoesNotExistError', 
        function(){new Effect.Scale('nothing-to-see-here')});
      assertRaise('ElementDoesNotExistError', 
        function(){new Effect.Highlight('nothing-to-see-here')});
    }},
    
    testCallbacks: function() { with(this) {
      tmp = tmp2 = 0;
      var e1 = new Effect.Opacity('sandbox',{from:1.0,to:0.5,duration:0.5,
        beforeStart: function() { tmp++ },
        beforeStartInternal: function() { tmp++ },
        beforeSetup: function() { tmp++ },
        beforeSetupInternal: function() { tmp++ },
        afterSetup: function() { tmp++ },
        afterSetupInternal: function() { tmp++ },
        beforeUpdate: function() { tmp2++ },
        beforeUpdateInternal: function() { tmp2++ },
        beforeFinish: function() { tmp++ },
        beforeFinishInternal: function() { tmp++ },
        afterFinish: function() { tmp++ },
        afterFinishInternal: function() { tmp++ }
      });
      wait(1000, function() {
        assertEqual(10, tmp);
        assert(tmp2 > 0);
      });
    }},
    
    testEvent: function() { with(this) {
      tmp = 0;
      new Effect.Event({ afterFinish:function(){ tmp++ }, position:'end'});
      wait(100, function() {
        assertEqual(1, tmp);
      });
    }},
    
    testTransition: function() { with(this) {
      // false implies linear
      var e = new Effect.Opacity('sandbox',{transition:false,from:0.0,to:0.25,duration:0.5});
      assert(e.options.transition == Effect.Transitions.linear);
      
      wait(1000, function() {
        assertEqual(0.25, $('sandbox').getStyle('opacity'));
        // default to sinoidal
        var e = new Effect.Opacity('sandbox',{from:0.0,to:0.25,duration:0.5});
        assert(e.options.transition == Effect.Transitions.sinoidal);
        wait(1000, function() {
          assertEqual(0.25, $('sandbox').getStyle('opacity'));
          
          var transitions = [
            { transition: Effect.Transitions.linear,   expected: 1 },
            { transition: Effect.Transitions.sinoidal, expected: 1 },
            { transition: Effect.Transitions.reverse,  expected: 0 },
            { transition: Effect.Transitions.flicker,  expected: 1 },
            { transition: Effect.Transitions.wobble,   expected: 1 },
            { transition: Effect.Transitions.pulse,    expected: 1 },
            { transition: Effect.Transitions.none,     expected: 0 }
          ];
          
          transitions.each(function(t){
            var e = new Effect.Opacity('sandbox',{sync:true, from:0, to: 1, transition:t.transition});
            assert(e.options.transition == t.transition);
            e.render(1.0);
            assertEqual(t.expected, e.position, t.transition);
          });
          
        });
      });
    }},
    
    testInspect: function() { with(this) {
      var e1 = new Effect.Opacity('sandbox',{from:1.0,to:0.5,duration:0.5});
      info( e1.inspect() );
      assertEqual(0, e1.inspect().indexOf('#<Effect:'));
      assert(e1.inspect().indexOf('idle')>0);
      wait(1000, function() {
        assert(e1.inspect().indexOf('finished')>0);
      });
    }},
    
    testDefaultOptions: function() { with(this) {
      var oldDefaultOptions = Object.extend({},Effect.DefaultOptions);
      
      assertEqual(1.0, Effect.DefaultOptions.duration);
      Effect.DefaultOptions.duration = 0.5;
      var e1 = new Effect.Opacity('sandbox');
      assertEqual(0.5, e1.options.duration);
      
      wait(750, function() {
        assertEqual('finished', e1.state);
        Effect.DefaultOptions = oldDefaultOptions;
      });
    }},
    
    testEffectsQueue: function() { with(this) {
      var e1 = new Effect.Highlight('sandbox');
      var e2 = new Effect.Appear('sandbox');
      
      assertEqual(2, Effect.Queue.effects.length);
      
      tmp = 0;
      Effect.Queue.each(function(e) { tmp++ });
      assertEqual(2, tmp);
      
      // the internal interval timer should be active
      assertNotNull(Effect.Queue.interval);
      e1.cancel();
      e2.cancel();
      assertEqual(0, Effect.Queue.effects.length);
      
      // should be inactive after all effects are removed from queue
      assertNull(Effect.Queue.interval);
      
      // should be in e3,e1,e2 order
      var e1 = new Effect.Highlight('sandbox');
      var e2 = new Effect.Appear('sandbox',{queue:'end'});
      var e3 = new Effect.Fade('sandbox',{queue:'front'});
      assert(e2.startOn > e1.startOn);
      assert(e3.startOn < e1.startOn);
      assert(e3.startOn < e2.startOn);
      assertEqual(3, Effect.Queue.effects.length);
      
      Effect.Queue.each(function(e) { e.cancel() });
      assertEqual(0, Effect.Queue.effects.length);
    }},
    
    testScopedEffectsQueue: function() { with(this) {
      var e1 = new Effect.Highlight('sandbox', {queue: {scope:'myscope'} } );
      var e2 = new Effect.Appear('sandbox', {queue: {scope:'myscope'} } );
      var e3 = new Effect.Highlight('sandbox', {queue: {scope:'secondscope'} } );
      var e4 = new Effect.Appear('sandbox');
     
      assertEqual(2, Effect.Queues.get('myscope').effects.length);
      assertEqual(1, Effect.Queues.get('secondscope').effects.length);
      assertEqual(1, Effect.Queues.get('global').effects.length);
      assertEqual(Effect.Queue.effects.length, Effect.Queues.get('global').effects.length);
     
      var tmp = 0;
      Effect.Queues.get('myscope').effects.each(function(e) { tmp++ });
      assertEqual(2, tmp);
     
      // the internal interval timer should be active
      assertNotNull(Effect.Queues.get('myscope').interval);
      assertNotNull(Effect.Queues.get('secondscope').interval);
      assertNotNull(Effect.Queues.get('global').interval);
    
      e1.cancel(); e2.cancel(); e3.cancel(); e4.cancel();
     
      assertEqual(0, Effect.Queues.get('myscope').effects.length);
      assertEqual(0, Effect.Queues.get('secondscope').effects.length);
      assertEqual(0, Effect.Queues.get('global').effects.length);
     
      // should be inactive after all effects are removed from queues
      assertNull(Effect.Queues.get('myscope').interval);
      assertNull(Effect.Queues.get('secondscope').interval);
      assertNull(Effect.Queues.get('global').interval);
     
      // should be in e3 and e4 together and then e1,e2 order
      var e1 = new Effect.Highlight('sandbox', {queue: {scope:'myscope'} } );
      var e2 = new Effect.Appear('sandbox', {queue: {position: 'end', scope:'myscope'} } );
      var e3 = new Effect.Fade('sandbox', {queue: {position: 'front', scope:'myscope'} } );
      var e4 = new Effect.Appear('sandbox');
      assert(e2.startOn > e1.startOn);
      assert(e3.startOn < e1.startOn);
      assert(e3.startOn < e2.startOn);
      assert(e3.startOn = e4.startOn);
      assertEqual(3, Effect.Queues.get('myscope').effects.length);
     
      Effect.Queues.get('myscope').each(function(e) { e.cancel() });
      assertEqual(0, Effect.Queues.get('myscope').effects.length);
    
      Effect.Queues.get('global').each(function(e) { e.cancel() });
      assertEqual(0, Effect.Queues.get('global').effects.length);
     
      // should only allow the first two effects and ignore the third
      var e1 = new Effect.Highlight('sandbox', {queue: {scope:'myscope', limit: 2} } );
      var e2 = new Effect.Appear('sandbox', {queue: {position: 'end', scope:'myscope', limit: 2} } );
      var e3 = new Effect.Fade('sandbox', {queue: {position: 'front', scope:'myscope', limit: 2} } );
     
      assertEqual(2, Effect.Queues.get('myscope').effects.length);
    }},
    
    testEffectMultiple: function() { with(this) {
      $('sandbox').appendChild(Builder.node('div',{id:'test_1'}));
      $('sandbox').appendChild(Builder.node('div',{id:'test_2'},[Builder.node('div',{id:'test_2a'})]));
      $('sandbox').appendChild(Builder.node('div',{id:'test_3'}));
      
      // only direct child elements
      Effect.multiple('sandbox',Effect.Fade);
      assertEqual(3, Effect.Queue.effects.length);
      
      Effect.Queue.each(function(e) { e.cancel() });
      assertEqual(0, Effect.Queue.effects.length);
      
      // call with array
      Effect.multiple(['test_1','test_3'],Effect.Puff);
      assertEqual(2, Effect.Queue.effects.length);
    }},
    
    testEffectTagifyText: function() { with(this) {
      $('sandbox').innerHTML = "Blah<strong>bleb</strong> Blub";
      assertEqual(3, $('sandbox').childNodes.length);
      Effect.tagifyText('sandbox');
      assertEqual(10, $('sandbox').childNodes.length);
      
      Effect.multiple('sandbox', Effect.Fade);
      assertEqual(10, Effect.Queue.effects.length);
    }},
    
    testEffectParallel: function() { with(this) {
      assert( new Effect.Parallel() );
      assert( new Effect.Parallel([]) );
      assert( new Effect.Parallel([],{}) );
      assert( new Effect.Parallel([
        new Effect.Event({sync:true})
      ],{ duration: 2}) );
    }},
    
    testEffectTween: function() { with(this) {
      // basic initialization
      assert(new Effect.Tween(null,0,0,function(){}));
      assert(new Effect.Tween(null,0,0,{duration:0.1},function(){}));

      // fun with objects
      var object = { 
        blech: 2, 
        foo: function(p){
          this.bar = p;
        }
      };
      assert(new Effect.Tween(object,0,1,{transition:Effect.Transitions.reverse},'blech'));
      assert(new Effect.Tween(object,0,1,'foo'));
      
      // fun with elements
      assert(new Effect.Tween('tween',50,1,'update'));
      
      wait(1500, function(){  
        assertEqual(0, object.blech);
        assertEqual(1, object.bar);
        assertEqual('1', $('tween').innerHTML);
      });
    }},

    // test if all combined effects correctly initialize themselves
    testCombinedEffectsInitialize: function() { with(this) {
      COMBINED_EFFECTS.each(function(fx,idx){
        info('Effect.'+fx);
        $('sandbox').innerHTML = "";
        $('sandbox').appendChild(
          Builder.node('div',{id:'test_element'},
            Builder.node('span','test'))); //some effects require a child element
            
        // should work with new Effect.Blah syntax
        var effect = new Effect[fx]('test_element');
        assertEqual(0, effect.currentFrame);
        
        // and without the 'new'
        var effect = Effect[fx]('test_element');
        assertEqual(0, effect.currentFrame);
        
        // visualEffect
        assert($('test_element') == $('test_element').visualEffect(COMBINED_RJS_EFFECTS[idx]));
        
        // direct element extension
        var method = COMBINED_EFFECTS[idx].charAt(0).toLowerCase() + COMBINED_EFFECTS[idx].substring(1)
        assert($('test_element') == $('test_element')[method]());
        
        // options parsing (shake, squish and grow are special here)
        if(!['Shake','Squish','Grow'].include(fx)) {
          var effect = Effect[fx]('test_element',{duration:2.0});
          assertEqual(2.0, effect.options.duration, fx);
        }
      });
    }},
    
    testSynchronizedEffects: function() { with(this) {
      var e1 = new Effect.Fade('sandbox',{sync:true});
      wait(250, function() {
        // effect should still be at frame 0
        assertEqual(0, e1.currentFrame);
        assertEqual('idle', e1.state);
        e1.render(0.01);
        
        // no frame count for sync effects
        assertEqual(0, e1.currentFrame);
        assertEqual('running', e1.state);
      });
    }},
    
    testEffectPosition: function() { with(this) {
      var testeffect = new Effect.Opacity('sandbox',{
        afterSetup:  function(effect) { effect.frames = 0; },
        afterUpdate: function(effect) { effect.frames++; $('sandbox').update(effect.position); },
        duration: 0.5, from: 1.0, to: 0.5
      });
      assertNull(testeffect.position);
      assertEqual('idle', testeffect.state);
      wait(1000, function() {
        info('Rendered ' + testeffect.frames + ' frames in .5 seconds ' +
          '(~' + (testeffect.frames/0.5) + 'fps of a possible 60fps, ' +
          'note that this can exceed 60fps because of additional last frame rendering)');
        assertEqual('0.5', $('sandbox').innerHTML);
        assertEqual(0.5, testeffect.position);
        assertEqual('finished', testeffect.state);
      });
    }},
    
    testRenderPerformance: function() { with(this) {
      info('The render() method is generated on a per-effect basis')
      var e = new Effect.Opacity('sandbox',{sync:true});
      benchmark(function(){
        e.render(0.5);
      },1000, 'Without events');
      var e = new Effect.Opacity('sandbox',{sync:true,afterUpdate:function(){return}});
      benchmark(function(){
        e.render(0.5);
      },1000, 'With afterUpdate event');      
    }},
    
    testElementMorph: function() { with(this) {
      $('error_test_ul').morph('font-size:40px', {duration: 0.5}).setStyle({marginRight:'17px'});
      $('error_message_2').morph({
        fontSize:         '20px',
        color:            '#f00',
        backgroundColor:  '#ffffff'
      },
      {
        duration:0.5
      });
      $('error_message_3').morph('final', {duration:0.5});
      wait(1000,function(){
        assertEqual('17px', $('error_test_ul').getStyle('margin-right'));
        assertEqual('40px', $('error_test_ul').getStyle('font-size'));
        assertEqual('#ffffff', $('error_message_2').getStyle('background-color').parseColor());
        assertEqual('20px', $('error_message_2').getStyle('font-size'));
        assertEqual('italic', $('error_message_3').getStyle('font-style'));
        assertEqual('20px', $('error_message_3').getStyle('font-size'));
        assertEqual(.5, $('error_message_3').getStyle('opacity'));
        assertEqual('', $('error_message_3').style.fontSize);
      });
    }},

    testElementMorphChaining: function() { with(this) {
      $('error_message').morph('font-size:17px').morph('opacity:0',{delay:2});
      wait(3100,function(){ // 2000ms delay + 1000ms default duration
        assertEqual(0, $('error_message').getStyle('opacity'));
      });
    }},

    testTransformBySelector: function() { with(this) {
      new Effect.Transform([
        { 'ul.error-list li': 'font-size:20px;text-indent:40pt' }
      ],{ duration: 0.5 }).play();

      wait(700,function(){
        var idx = 0;
        $A($('error_test_ul').cleanWhitespace().childNodes).each(function(node){
          assertEqual('20px', $(node).getStyle('font-size'));
          assertEqual('40pt', $(node).getStyle('text-indent'));
          idx++;
        });
        assertEqual(5, idx);
      });
    }},

    testTransformUsesCSSClassPresets: function() { with(this) {
      assertEqual('40px', $('rotfl').getStyle('font-size'));

      // Render the effect at half-way through, font-size should be
      // exactly half-way between original and target
      new Effect.Transform([
        { 'rotfl': 'font-size:20px;text-indent:40pt;background-color:#888' }
      ],{ sync:true }).play().render(0.5);

      wait(1100,function(){
        // should be 30px = 40px + (20px-40px)/2
        assertEqual('30px', $('rotfl').getStyle('font-size'));
      });
    }},

    testTransformMultiple: function() { with(this) {
      var transformation = new Effect.Transform([
        { 'div.morphing': 'font-size:20px;padding-left:40em;opacity:0.5' },
        { 'blah'        : 
          'width:480px;border-width:10px;border-right-width:20px;' +
          'margin:20px;margin-bottom:-20px;font-size:30px;' +
          'background:#954' }
      ],{ duration: 0.5 });

      var generatedEffect = transformation.play();

      assertEqual(3, generatedEffect.effects.length);

      wait(700, function(){
        // have a look at the generated color transforms for the 3rd found element
        // which is the "blah" div
        assertEqual('blah', generatedEffect.effects[2].element.id);
        assertEnumEqual([255,255,255], 
          generatedEffect.effects[2].transforms.detect( function(transform){
            return (transform.style == 'backgroundColor')
          }).originalValue);
        assertEnumEqual([153,85,68], 
          generatedEffect.effects[2].transforms.detect( function(transform){
            return (transform.style == 'backgroundColor')
        }).targetValue);

        assertEqual('20px', $$('div.morphing').first().getStyle('font-size'));
        assertEqual('20px', $$('div.morphing').last().getStyle('font-size'));
        assertEqual('30px', $('blah').getStyle('font-size'));

        // border-width/border-right-width should be set independently
        assertEqual('10px', $('blah').getStyle('border-top-width'));
        assertEqual('10px', $('blah').getStyle('border-bottom-width'));
        assertEqual('10px', $('blah').getStyle('border-left-width'));
        assertEqual('20px', $('blah').getStyle('border-right-width'));

        // colors should assume transition from 
        // #ffffff (white) if original was transparent
        // we now should have arrived at the given color
        assertEqual('#995544', $('blah').getStyle('background-color').parseColor());

        // play again = should have same values
        transformation.play();
        wait(700, function(){
          assertEqual('20px', $$('div.morphing').first().getStyle('font-size'));
          assertEqual('20px', $$('div.morphing').last().getStyle('font-size'));
          assertEqual('30px', $('blah').getStyle('font-size'));

          $('blah').setStyle({fontSize:'100px'});
          assertEqual('100px', $('blah').getStyle('font-size'));        
          transformation.play();
          wait(700, function(){
            assertEqual('30px', $('blah').getStyle('font-size'));

            new Effect.Transform([
              { 'blah': 'color: #80d980; background: #208020' }
            ],{ duration: 1.1 }).play();
            wait(1500, function(){
              assertEqual('#80d980', $('blah').getStyle('color').parseColor());
              assertEqual('#208020', $('blah').getStyle('background-color').parseColor());
            });
          });
        });
      });
    }}

  });

// ]]>
</script>
</body>
</html>
